package com.demo2do.fms.queue.service.impl;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.fms.entity.authority.Organization;
import com.demo2do.fms.entity.authority.User;
import com.demo2do.fms.entity.driver.Driver;
import com.demo2do.fms.entity.driver.DriverAuditStatus;
import com.demo2do.fms.entity.supplier.common.Supplier;
import com.demo2do.fms.entity.supplier.common.Type;
import com.demo2do.fms.entity.vam.OperationType;
import com.demo2do.fms.entity.vehicle.AssetStatus;
import com.demo2do.fms.entity.vehicle.AuditStatus;
import com.demo2do.fms.entity.vehicle.DeclareStatus;
import com.demo2do.fms.entity.vehicle.ModifiedStatus;
import com.demo2do.fms.entity.vehicle.source.*;
import com.demo2do.fms.entity.vehicle.tractor.*;
import com.demo2do.fms.entity.vehicle.trailer.*;
import com.demo2do.fms.queue.Constants;
import com.demo2do.fms.queue.dao.*;
import com.demo2do.fms.queue.dao.event.TractorEventDao;
import com.demo2do.fms.queue.dao.event.TrailerEventDao;
import com.demo2do.fms.queue.entity.RemoteVehicle;
import com.demo2do.fms.queue.entity.VehicleBind;
import com.demo2do.fms.queue.entity.VehicleDelete;
import com.demo2do.fms.queue.handler.VamHandler;
import com.demo2do.fms.queue.service.VehicleService;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

/**
 *
 * @author Ark
 *
 */
@Service("vehicleService")
public class VehicleServiceImpl implements VehicleService {
    
    @Value("#{config['fileservice.url']}")
    private String visitUrl;
    
    @Dao
    private TractorDao tractorDao;
    
    @Dao
    private TrailerDao trailerDao;
    
    @Dao
    private TractorModifiedVersionDao tractorModifiedVersionDao;
    
    @Dao
    private TrailerModifiedVersionDao trailerModifiedVersionDao;
    
    @Dao
    private VehicleSourceDao vehicleSourceDao;
    
    @Dao
    private VehicleDao vehicleDao;
    
    @Dao
    private DriverDao driverDao;
    
    @Dao
    private RemoteTransMeansDao remoteTransMeansDao;
    
    @Autowired
    private GenericDaoSupport genericDaoSupport;
    
    @Dao
    private SupplierDao supplierDao;
    
    @Autowired
    private VamHandler vamHandler;
    
    @Dao
    private TractorEventDao tractorEventDao;
    
    @Dao
    private TrailerEventDao trailerEventDao;

    private final static Logger logger = LoggerFactory.getLogger(VehicleServiceImpl.class);
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.queue.service.RemoteVehicleService#save(com.demo2do.fms.entity.remote.RemoteVehicle)
     */
    public void save(RemoteVehicle remoteVehicle) {
        
        // validate
        this.valid(remoteVehicle, OperationType.CREATE);
        
        // save
        this.create(remoteVehicle);
        
    }
    
    /**
     * 校验VAM车辆必填项和数据合法性
     * 
     * @param vehicle
     */
    private void valid(RemoteVehicle vehicle, OperationType type) {
        
        // 1. 校验资产分类合法性
        boolean validVehicleCls = vehicle.validVehicleCls();
        if (!validVehicleCls) {
            throw BusinessException.newInstance("500", "资产分类无效");
        }
    
        if (vehicle.isTractor() || vehicle.isRescue()) {
            
            // 2. 验证必填项
            String tractorRequired = vehicle.validTractorRequired();
            if (StringUtils.isNotBlank(tractorRequired)) {
                throw BusinessException.newInstance("500", tractorRequired);
            }
            
            // 3. 校验数据合法性 & 唯一性
            String tractorValid = this.validTractor(vehicle, type);
            if (StringUtils.isNotBlank(tractorValid)) {
                throw BusinessException.newInstance("500", tractorValid);
            }
            
        } else if (vehicle.isTrailer()) {
            
            // 2. 校验必填项
            String trailerRequired = vehicle.validTrailerRequired();
            if (StringUtils.isNotBlank(trailerRequired)) {
                throw BusinessException.newInstance("500", trailerRequired);
            }
            
            // 3. 校验数据合法性 & 唯一性
            String trailerValid = this.validTrailer(vehicle, type);
            if (StringUtils.isNotBlank(trailerValid)) {
                throw BusinessException.newInstance("500", trailerValid);
            }
            
        }
        
    }

    /**
     * 牵引车合法性校验
     * 
     * @param vehicle
     * @return
     */
    /**
     * @param vehicle
     * @return
     */
    private String validTractor(RemoteVehicle vehicle, OperationType type) {
        
        // 1.1 中文车牌号
        if (!Pattern.matches("[\u4e00-\u9fa5]{1}[\u4e00-\u9fa5a-zA-Z0-9]*", vehicle.getBrandCnCode())) {
            return "中文车牌号不合法";
        }
        
        // 1.2 英文车牌号
        if (!Pattern.matches("[A-Za-z0-9]+", vehicle.getBrandEnCode())) {
            return "英文车牌号不合法";
        }
        
        // 1.3 车辆类型
        TractorVehicleType vehicleType = TractorVehicleType.valueOf(vehicle.getVehicleType());
        if (vehicleType == null) {
            return "车辆类型不合法";
        }
        
        // 1.4 车辆所属权
        CarOwnership carOwnership = CarOwnership.valueOf(vehicle.getCarOwnership());
        if (carOwnership == null) {
            return "车辆所属权不合法";
        }
        
        // 1.5 运输工具类型
        TransMean transMean = TransMean.valueOf(vehicle.getTransMean());
        if (transMean == null) {
            return "运输工具类型不合法";
        }
        
        // 1.6 资产状态
        AssetStatus assetStatus = AssetStatus.valueOf(vehicle.getAssetStatus());
        if (assetStatus == null) {
            return "资产状态不合法";
        }
        
        if (type.isCreate()) {
            
            // 2.1 中文车牌号唯一性
            Tractor existTractor = tractorDao.getByBrandCNCode(vehicle.getBrandCnCode());
            if (existTractor != null) {
                return "中文车牌号已存在";
            }
            
            // 2.2 sim卡号唯一性
            String simCard = vehicle.getSimCard();
            if (StringUtils.isNotBlank(simCard)) {
                existTractor = tractorDao.getBySimcard(vehicle.getSimCard());
                if (existTractor != null) {
                    return "sim卡号已存在";
                }
            }
            
        }
        
        // 3. 创建人校验
        User user = genericDaoSupport.get(User.class, vehicle.getCreator());
        if (user == null) {
            return "创建人不存在";
        }
        
        // 4. 校验使用方公司
        Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
        if (organization == null) {
            return "车辆使用方公司无效";
        }
        
        return null;
        
    }

    /**
     * 校验挂车数据合法性
     * 
     * @param vehicle
     * @return
     */
    private String validTrailer(RemoteVehicle vehicle, OperationType type) {
        
        // 1.1 中文车牌号
        if (!Pattern.matches("[\u4e00-\u9fa5]{1}[\u4e00-\u9fa5a-zA-Z0-9]*", vehicle.getBrandCnCode())) {
            return "中文车牌号不合法";
        }
        
        // 1.2 车辆类型（行驶证）
        TrailerVehicleType vehicleType = TrailerVehicleType.valueOf(vehicle.getVehicleType());
        if (vehicleType == null) {
            return "车辆类型（行驶证）不合法";
        }
        
        // 1.3 资产状态
        AssetStatus assetStatus = AssetStatus.valueOf(vehicle.getAssetStatus());
        if (assetStatus == null) {
            return "资产状态不合法";
        }
        
        // 1.4 挂车类型
        TrailerType trailerType = TrailerType.valueOf(vehicle.getTrailerType());
        if (trailerType == null) {
            return "挂车类型不合法";
        }
        
        // 2. 中文车牌号唯一性
        if (type.isCreate()) {
            Trailer existTrailer = trailerDao.getByBrandCNCode(vehicle.getBrandCnCode());
            if (existTrailer != null) {
                return "中文车牌号已存在";
            }
        }
        
        // 3. 创建人校验
        User user = genericDaoSupport.get(User.class, vehicle.getCreator());;
        if (user == null) {
            return "创建人不存在";
        }
        
        // 4. 校验使用方公司
        Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
        if (organization == null) {
            return "车辆使用方公司无效";
        }
        
        return null;
        
    }

    /**
     * 创建车辆
     * 
     * @param vehicle
     */
    private void create(RemoteVehicle vehicle) {
        
        String floorLord = vehicle.getFloorLord();                      // 产证所有者
        String brandCnCode = vehicle.getBrandCnCode();                  // 中文车牌号
        String vincode = vehicle.getVincode();                          // vin码
        String brand = vehicle.getBrand();                              // 牵引车品牌
        BigDecimal axles = vehicle.getAxles();                          // 轴数
        Date annualInspectionDate = vehicle.getAnnualInspectionDate();  // 年检有效期
        BigDecimal trainload = vehicle.getTrainload();                  // 装载量
        AssetStatus assetStatus = AssetStatus.valueOf(vehicle.getAssetStatus());            // 资产状态
        Long vamId = vehicle.getVamId();                                // vam车辆id
        String drivingLicense = visitUrl + vehicle.getDrivingLicense();            // 行驶证
        String drivingLicenseCopy = visitUrl + vehicle.getDrivingLicenseCopy();    // 行驶证副本
        String vehiclePhoto = visitUrl + vehicle.getVehiclePhoto();                // 45度角照片
        
        if (vehicle.isTractor() || vehicle.isRescue()) {
            
            TractorVehicleType vehicleType = TractorVehicleType.valueOf(vehicle.getVehicleType());      // 车辆类型
            CarOwnership carOwnership = CarOwnership.valueOf(vehicle.getCarOwnership());    // 车辆所属权
            TransMean transMean = TransMean.valueOf(vehicle.getTransMean());                // 运输工具类型
            String brandEnCode = vehicle.getBrandEnCode();                  // 英文车牌号
            BigDecimal horsepower = vehicle.getHorsepower();                    // 马力
            String gps = vehicle.getGps();                                  // gps
            String simCard = vehicle.getSimCard();                          // sim卡号
            Date issuingTime = vehicle.getIssuingTime();                    // 发证时间
            Date registrationTime = vehicle.getRegistrationTime();          // 注册时间
            String vehicleContact = null;									// 购车合同
            if (!StringUtils.isBlank(vehicle.getVehicleContact())) {
                vehicleContact = visitUrl + vehicle.getVehicleContact();
			}
            User creator = genericDaoSupport.get(User.class, vehicle.getCreator());
            Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
            Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
            
			Tractor tractor = new Tractor(floorLord, carOwnership, transMean, brandCnCode, brandEnCode, vincode, brand, horsepower, gps, simCard, issuingTime, registrationTime, axles, annualInspectionDate, trainload, vehicleType, drivingLicense, drivingLicenseCopy, vehiclePhoto, vehicleContact, organization, supplier, assetStatus, vamId, creator.getId(), creator.getId());
            tractor = this.syncOldTractor(tractor);
            genericDaoSupport.save(tractor);
            
            genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CREATE, creator.getId()));
            
        } else if (vehicle.isTrailer()) {
    
            TrailerVehicleType vehicleType = TrailerVehicleType.valueOf(vehicle.getVehicleType());
            TrailerType trailerType = TrailerType.valueOf(vehicle.getTrailerType());
            BigDecimal length = vehicle.getLength(); 
            BigDecimal width = vehicle.getWidth();
            BigDecimal height = vehicle.getHeight();
            
            User creator = genericDaoSupport.get(User.class, vehicle.getCreator());;
            Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
            Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
            
            Trailer trailer = new Trailer(floorLord, trailerType, brandCnCode, brand, vincode, length, width, height, trainload, annualInspectionDate, axles, vehicleType, drivingLicense, drivingLicenseCopy, vehiclePhoto, organization, supplier, assetStatus, vamId, creator.getId()); 
            genericDaoSupport.save(trailer);
            
            genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CREATE, creator.getCreator()));
            
        }
        
    }
    
    /**
     * @param tractor
     * @return
     */
    private Tractor syncOldTractor(Tractor tractor) {
        
        String brandCNCode = tractor.getBrandCNCode();
        
        Tractor existTractor = tractorDao.getByBrandCNCode(brandCNCode, true);
        if(existTractor != null && StringUtils.isNotBlank(existTractor.getCode())) {
            
            // 同步老车辆信息
            tractor.editCode(existTractor.getCode());
            
        }
        
        return tractor;
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.queue.service.RemoteVehicleService#update(com.demo2do.fms.entity.remote.RemoteVehicle)
     */
    public void update(RemoteVehicle remoteVehicle) {
        
        if (remoteVehicle.isTractor() || remoteVehicle.isRescue()) {
            
            // 1. 查看车辆状态，根据状态判断是否可更新
            Tractor existTractor = tractorDao.getByVamid(remoteVehicle.getVamId());
            if (existTractor == null) {
                throw BusinessException.newInstance("500", "牵引车不存在");
            }
            
            AuditStatus auditStatus = existTractor.getStatus();
            if (auditStatus.isAuditing() || auditStatus.isModified()) {
                throw BusinessException.newInstance("500", "审核中或变更申报中");
            }
            
            // 2. 数据校验
            this.valid(remoteVehicle, OperationType.UPDATE);
            
            // 3. 如果为转籍，需要进行转籍操作
            boolean transfer = existTractor.getOrganization().getId().longValue() != remoteVehicle.getOrganizationId().longValue();
            if (transfer) {
                this.transfer(existTractor, remoteVehicle);
            }
            
            // 4. 更新
            if (!transfer) {
                this.editTractor(remoteVehicle, existTractor);
            }
            
            
        } else {
            
            // 1. 查看车辆状态，根据状态判断是否可更新
            Trailer existTrailer = trailerDao.getByVamid(remoteVehicle.getVamId());
            if (existTrailer == null) {
                throw BusinessException.newInstance("500", "挂车不存在");
            }
            
            AuditStatus auditStatus = existTrailer.getStatus();
            if (auditStatus.isAuditing() || auditStatus.isModified()) {
                throw BusinessException.newInstance("500", "正在递交审核或变更申报中");
            }
            
            // 2. 数据校验
            this.valid(remoteVehicle, OperationType.UPDATE);
            
            // 3. 如果为转籍，需要进行转籍操作
            boolean transfer = existTrailer.getOrganization().getId().longValue() != remoteVehicle.getOrganizationId().longValue(); 
            if (transfer) {
                this.transfer(existTrailer, remoteVehicle);
            }
            
            // 4. 更新
            if (!transfer) {
                this.editTrailer(remoteVehicle, existTrailer);
            }
            
        }
        
    }
    
    /**
     * @param remoteVehicle
     * @param tractor
     */
    public void editTractor(RemoteVehicle remoteVehicle, Tractor tractor) {
        
        Tractor newTractor = this.constructTractor(remoteVehicle);
        AuditStatus auditStatus = tractor.getStatus();
        DeclareStatus declareStatus = tractor.getDeclareStatus();
        
        if (auditStatus.isRejected() && declareStatus.isUnsync()) {         // 引入申报已驳回
            genericDaoSupport.update(tractor.edit(newTractor).status(AuditStatus.AUDITING));
        }
        
        boolean change = this.changeTractor(newTractor, tractor);
        
        boolean redirectEdit1 = auditStatus.isQualified() && !change;       // 合格，不涉及变更
        boolean redirectEdit2 = auditStatus.isRejected() && declareStatus.isModified() && !change;     // 变更申报已驳回，不涉及变更
        if (redirectEdit1 || redirectEdit2) {
            
            genericDaoSupport.update(tractor.edit(newTractor));
            
            // send audit info to vam
            vamHandler.syncVehicleStatus("tractor", tractor, null);
            
        }
        
        boolean changeEdit1 = auditStatus.isQualified() && change;
        boolean changeEdit2 = auditStatus.isRejected() && declareStatus.isModified() && change;
        if (changeEdit1 || changeEdit2) {
            
            genericDaoSupport.update(tractor.editQualifiedField(newTractor));   // 变更申报同时，改了无需变更申报的字段
            
            // construct modify version
            TractorModifiedVersion lastedVersion = tractorModifiedVersionDao.load(tractor.getId(), ModifiedStatus.AUDITING);
            int version = lastedVersion == null ? 1 : lastedVersion.getVersionSorting() + 1;
            TractorModifiedVersion currentVersion = new TractorModifiedVersion(tractor, newTractor, version);
            genericDaoSupport.save(currentVersion);
            
            // change tractor
            genericDaoSupport.update(tractor.change(newTractor.getCreator()));
            genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CHANGE, newTractor.getCreator()));
            
        }
        
        // edit gps info
        this.updateGps(tractor, newTractor.getSimCard());
        
    }
    
    /**
     * @param tractor
     */
    private void updateGps(Tractor tractor, String simCard) {
        String code = tractor.getCode();
        if (StringUtils.isNotBlank(code)) {
            RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(code);
            if( remoteTransMeans != null && !StringUtils.equals(simCard, remoteTransMeans.getGpsDeviceNumber()) ) {
                genericDaoSupport.update(remoteTransMeans.editGps(simCard, new Date()));
            }
        }
    }
    
    /**
     * 合格车辆，判断是否有涉及变更
     * 
     * @param newTractor
     * @param existTractor
     * @return
     */
    private boolean changeTractor(Tractor newTractor, Tractor existTractor) {
        if (!StringUtils.equals(newTractor.getFloorLord(), existTractor.getFloorLord())) {
            return true;
        } else if (!newTractor.getCarOwnership().equals(existTractor.getCarOwnership())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getVincode(), existTractor.getVincode())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getBrand(), existTractor.getBrand())) {
            return true;
        } else if (!DateUtils.isSameInstant(newTractor.getIssuingTime(), existTractor.getIssuingTime())) {
            return true;
        } else if (!newTractor.getHorsepower().equals(existTractor.getHorsepower())) {
            return true;
        } else if (!DateUtils.isSameInstant(newTractor.getRegistrationTime(), existTractor.getRegistrationTime())) {
            return true;
        } else if (!DateUtils.isSameInstant(newTractor.getAnnualInspectionDate(), existTractor.getAnnualInspectionDate())) {
            return true;
        } else if (!newTractor.getAxles().equals(existTractor.getAxles())) {
            return true;
        } else if (!newTractor.getVehicleType().equals(existTractor.getVehicleType())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getDrivingLicense(), existTractor.getDrivingLicense())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getDrivingLicenseCopy(), existTractor.getDrivingLicenseCopy())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getVehiclePhoto(), existTractor.getVehiclePhoto())) {
            return true;
        } else if (!StringUtils.equals(newTractor.getVehicleContact(), existTractor.getVehicleContact())) {
            return true;
        } else if (!newTractor.getTrainload().equals(existTractor.getTrainload())) {
            return true;
        }
        return false;
    }
    
    /**
     * @param remoteVehicle
     * @param trailer
     */
    public void editTrailer(RemoteVehicle remoteVehicle, Trailer trailer) {
        
        Trailer newTrailer = this.constructTrailer(remoteVehicle);
        AuditStatus auditStatus = trailer.getStatus();
        DeclareStatus declareStatus = trailer.getDeclareStatus();
        
        if (auditStatus.isRejected() && declareStatus.isUnsync()) {      // 引入申报已驳回
            genericDaoSupport.update(trailer.edit(newTrailer).status(AuditStatus.AUDITING));
        }

        boolean change = this.changeTrailer(newTrailer, trailer);
        
        boolean redirectEdit1 = auditStatus.isQualified() && !change;       // 合格，不涉及变更
        boolean redirectEdit2 = auditStatus.isRejected() && declareStatus.isModified() && !change;     // 变更申报已驳回，不涉及变更
        if (redirectEdit1 || redirectEdit2) {
            
            genericDaoSupport.update(trailer.edit(newTrailer));
            
            // send audit info to vam
            vamHandler.syncVehicleStatus("trailer", null, trailer);
            
        }
        
        boolean changeEdit1 = auditStatus.isQualified() && change;
        boolean changeEdit2 = auditStatus.isRejected() && declareStatus.isModified() && change;
        if (changeEdit1 || changeEdit2) {    // 合格， 涉及变更
            
            genericDaoSupport.update(trailer.editQualifiedField(newTrailer));   // 变更申报同时，改了无需变更申报的字段
            
            // construct modify version
            TrailerModifiedVersion lastedVersion = trailerModifiedVersionDao.load(trailer.getId(), ModifiedStatus.AUDITING);
            int version = lastedVersion == null ? 1 : lastedVersion.getVersionSorting() + 1;
            TrailerModifiedVersion currentVersion = new TrailerModifiedVersion(trailer, newTrailer, version);
            genericDaoSupport.save(currentVersion);
         
            // update trailer to change
            genericDaoSupport.update(trailer.change(newTrailer.getCreator()));
            genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CHANGE, newTrailer.getCreator()));
            
        }
        
    }
    
    /**
     * 合格车辆，判断是否有涉及变更
     * 
     * @param newTrailer
     * @param existTrailer
     * @return
     */
    private boolean changeTrailer(Trailer newTrailer, Trailer existTrailer) {
        if (!StringUtils.equals(newTrailer.getFloorLord(), existTrailer.getFloorLord())) {
            return true;
        } else if (!newTrailer.getTrailerType().equals(existTrailer.getTrailerType())) {
            return true;
        } else if (!StringUtils.equals(newTrailer.getVincode(), existTrailer.getVincode())) {
            return true;
        } else if (!newTrailer.getTrainload().equals(existTrailer.getTrainload())) {
            return true;
        } else if (!newTrailer.getVehicleType().equals(existTrailer.getVehicleType())) {
            return true;
        } else if (!StringUtils.equals(newTrailer.getBrand(), existTrailer.getBrand())) {
            return true;
        } else if (!StringUtils.equals(newTrailer.getDrivingLicense(), existTrailer.getDrivingLicense())) {
            return true;
        } else if (!StringUtils.equals(newTrailer.getDrivingLicenseCopy(), existTrailer.getDrivingLicenseCopy())) {
            return true;
        } else if (!StringUtils.equals(newTrailer.getVehiclePhoto(), existTrailer.getVehiclePhoto())) {
            return true;
        }
        return false;
    }
    
    /**
     * 牵引车转籍
     * 
     * @param tractor
     * @param remoteVehicle
     */
    private void transfer(Tractor tractor, RemoteVehicle remoteVehicle) {
        
        Long operator = remoteVehicle.getLastOperator();
        Date now = new Date();
        Organization organization = genericDaoSupport.load(Organization.class, remoteVehicle.getOrganizationId());
        Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
        
        // 3. handle trailer 、 vehicle_source
        if (tractor.isBind()) {
            
            VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());      // 删除整车
            genericDaoSupport.update(vehicleSource.disable(operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            if (vehicleSource.hasTrailer()) {                                           // 解绑挂车
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
            if(vehicleSource.isSynchronize()) {                                                 // 解除司机和车辆绑定关系
                this.lockVehicle(vehicleSource, "转籍牵引车冻结", LockType.FREEZE, operator, "转籍牵引车冻结");
            }
            
        }
        
        // 1. transfer tractor
        tractor = tractor.transferForVam(supplier, organization, operator);
        
        // 2. save tractor event
        tractorEventDao.update(supplier, tractor);
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.TRANSFER, operator));
        
        // 4. update tractor
        Tractor newTractor = this.constructTractor(remoteVehicle);
        genericDaoSupport.update(tractor.edit(newTractor));
        this.updateGps(tractor, newTractor.getSimCard());
        
    }
    
    /**
     * 转籍挂车
     * 
     * @param trailer
     * @param remoteVehicle
     */
    private void transfer(Trailer trailer, RemoteVehicle remoteVehicle) {
        
        Long operator = remoteVehicle.getLastOperator();
        Date now = new Date();
        Organization organization = genericDaoSupport.load(Organization.class, remoteVehicle.getOrganizationId());
        Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
        
        // 3. handle trailer、vehicle_source
        if (trailer.isBind()) {
            
            VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());      // 删除整车
            genericDaoSupport.update(vehicleSource.disable(operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());  // 解绑牵引车
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
            if(vehicleSource.couldLock()) {                                                 // 解除司机和车辆绑定关系
                this.lockVehicle(vehicleSource, "挂车解绑，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "转籍挂车锁定");
            }
            
        }
        // 1. transfer trailer
        trailer = trailer.transfer(supplier, organization, operator);
        
        // 2. save trailer
        trailerEventDao.update(supplier, trailer);
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.TRANSFER, operator));
        
        // 4. update trailer
        Trailer newTrailer = this.constructTrailer(remoteVehicle);
        genericDaoSupport.update(trailer.edit(newTrailer));
        
    }
    
    /**
     * 构造牵引车
     * 
     * @param vehicle
     * @return
     */
    public Tractor constructTractor(RemoteVehicle vehicle) {
        
        String floorLord = vehicle.getFloorLord();                      // 产证所有者
        String brandCnCode = vehicle.getBrandCnCode();                  // 中文车牌号
        String vincode = vehicle.getVincode();                          // vin码
        String brand = vehicle.getBrand();                              // 牵引车品牌
        BigDecimal axles = vehicle.getAxles();                          // 轴数
        Date annualInspectionDate = vehicle.getAnnualInspectionDate();  // 年检有效期
        BigDecimal trainload = vehicle.getTrainload();                  // 装载量
        AssetStatus assetStatus = AssetStatus.valueOf(vehicle.getAssetStatus());            // 资产状态
        Long vamId = vehicle.getVamId();
        String drivingLicense = visitUrl + vehicle.getDrivingLicense();            // 行驶证
        String drivingLicenseCopy = visitUrl + vehicle.getDrivingLicenseCopy();    // 行驶证副本
        String vehiclePhoto = visitUrl + vehicle.getVehiclePhoto();                // 45度角照片
        
        TractorVehicleType vehicleType = TractorVehicleType.valueOf(vehicle.getVehicleType());      // 车辆类型
        CarOwnership carOwnership = CarOwnership.valueOf(vehicle.getCarOwnership());    // 车辆所属权
        TransMean transMean = TransMean.valueOf(vehicle.getTransMean());                // 运输工具类型
        String brandEnCode = vehicle.getBrandEnCode();                  // 英文车牌号
        BigDecimal horsepower = vehicle.getHorsepower();                    // 马力
        String gps = vehicle.getGps();                                  // gps
        String simCard = vehicle.getSimCard();                          // sim卡号
        Date issuingTime = vehicle.getIssuingTime();                    // 发证时间
        Date registrationTime = vehicle.getRegistrationTime();          // 注册时间
        String vehicleContact = null;         							// 购车合同
        if (!StringUtils.isBlank(vehicle.getVehicleContact())) {
            vehicleContact = visitUrl + vehicle.getVehicleContact();
		}
        User creator = genericDaoSupport.get(User.class, vehicle.getCreator());
        User lastOperator = genericDaoSupport.get(User.class, vehicle.getLastOperator());
        Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
        Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
        
        Tractor tractor = new Tractor(floorLord, carOwnership, transMean, brandCnCode, brandEnCode, vincode, brand, horsepower, gps, simCard, issuingTime, registrationTime, axles, annualInspectionDate, trainload, vehicleType, drivingLicense, drivingLicenseCopy, vehiclePhoto, vehicleContact, organization, supplier, assetStatus, vamId, creator.getId(), lastOperator.getId());
        return tractor;
        
    }
    
    /**
     * 构造挂车
     * 
     * @param vehicle
     * @return
     */
    public Trailer constructTrailer(RemoteVehicle vehicle) {
        
        String floorLord = vehicle.getFloorLord();          // 产证所有者
        String brandCnCode = vehicle.getBrandCnCode();                  // 中文车牌号
        String vincode = vehicle.getVincode();                          // vin码
        String brand = vehicle.getBrand();                              // 牵引车品牌
        BigDecimal axles = vehicle.getAxles();                          // 轴数
        Date annualInspectionDate = vehicle.getAnnualInspectionDate();  // 年检有效期
        BigDecimal trainload = vehicle.getTrainload();                  // 装载量
        AssetStatus assetStatus = AssetStatus.valueOf(vehicle.getAssetStatus());            // 资产状态
        Long vamId = vehicle.getVamId();
        String drivingLicense = visitUrl + vehicle.getDrivingLicense();            // 行驶证
        String drivingLicenseCopy = visitUrl + vehicle.getDrivingLicenseCopy();    // 行驶证副本
        String vehiclePhoto = visitUrl + vehicle.getVehiclePhoto();                // 45度角照片
        
        TrailerVehicleType vehicleType = TrailerVehicleType.valueOf(vehicle.getVehicleType());
        TrailerType trailerType = TrailerType.valueOf(vehicle.getTrailerType());
        BigDecimal length = vehicle.getLength(); 
        BigDecimal width = vehicle.getWidth();
        BigDecimal height = vehicle.getHeight();
        
        User user = genericDaoSupport.get(User.class, vehicle.getCreator());;
        Organization organization = genericDaoSupport.load(Organization.class, vehicle.getOrganizationId());
        Supplier supplier = supplierDao.get(organization.getId(), Type.MOTOR);
        
        Trailer trailer = new Trailer(floorLord, trailerType, brandCnCode, brand, vincode, length, width, height, trainload, annualInspectionDate, axles, vehicleType, drivingLicense, drivingLicenseCopy, vehiclePhoto, organization, supplier, assetStatus, vamId, user.getId());
        return trailer;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.queue.service.RemoteVehicleService#bind(com.demo2do.fms.queue.entity.VehicleBind)
     */
    public void bind(VehicleBind vehicleBind) {
        
        Date now = new Date();
        Long operator = vehicleBind.getOperator();
        if (operator == null) {
            throw BusinessException.newInstance("500", "操作人不能为空");
        }
        
        if (vehicleBind.isBind()) {             // 车辆绑定

            // 1. 判断是绑定还是换挂
            Tractor tractor = tractorDao.getByVamid(vehicleBind.getTractorId());
            if (tractor.isBind()) {              
                
                // *******************    换挂逻辑        ******************************
                Trailer changeTrailer = trailerDao.getByVamid(vehicleBind.getTrailerId());
                
                // 2. 被更换整车解绑
                if (changeTrailer.isBind())  {
                    
                    VehicleSource changeVehicleSource = vehicleSourceDao.loadByTrailer(changeTrailer.getId());
                    genericDaoSupport.update(changeVehicleSource.disable(operator));
                    if(changeVehicleSource.couldLock()) {
                        this.lockVehicle(changeVehicleSource, "换挂，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "更换挂车锁定");
                    }
                    
                    Tractor changeTractor = genericDaoSupport.load(Tractor.class, changeVehicleSource.getTractorId());
                    genericDaoSupport.update(changeTractor.unbind().operate(now, operator));
                    
                }
                
                // 3. 本整车换挂
                VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
                if (vehicleSource.hasTrailer()) {
                    Trailer originalTrailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                    genericDaoSupport.update(originalTrailer.unbind().operate(now, operator));
                }
                
                genericDaoSupport.update(vehicleSource.editTrailer(changeTrailer).operate(now, operator));
                genericDaoSupport.update(changeTrailer.bind().sign(vehicleSource.getSynchronize()).operate(now, operator));
                
                // 3. save vehicle_source event
                genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.CHANGE_TRAILER, operator));
                
            } else {
                
                // *******************    绑定逻辑        ******************************

                // 2. valid bind
                this.validBind(tractor, vehicleBind.getTrailerId());
                
                Supplier supplier = tractor.getSupplier();
                Organization organization = tractor.getOrganization();
                
                // 3. initialize vehicle_source
                VehicleSource source = null;
                if (vehicleBind.getTrailerId() == null) {
                    
                    source = new VehicleSource(tractor, supplier, organization, now, operator);
                    source.editVam(true);
                    
                    genericDaoSupport.update(tractor.bind().operate(now, operator));
                    
                } else {
                    
                    Trailer trailer = trailerDao.getByVamid(vehicleBind.getTrailerId());
                    if (trailer.isBind()) {
                        throw BusinessException.newInstance("500", "该挂车已绑定");
                    }
                    
                    source = new VehicleSource(tractor, trailer, supplier, organization, now, operator);
                    source.editVam(true);
                    
                    genericDaoSupport.update(tractor.bind().operate(now, operator));
                    genericDaoSupport.update(trailer.bind().operate(now, operator));
                    
                }
                
                // 4. sync old vehicle info
                source = this.syncOldVehicleSource(source);
                genericDaoSupport.save(source);
                
                // 5. save vehicle_source event
                genericDaoSupport.save(new VehicleSourceEvent(source, VehicleSourceEventType.BIND, operator));
                
                // 6. sync vehicle to synced_status
                this.sync(source, operator, now);
                
            }
            
        } else {
            
            // *******************    解绑逻辑        ******************************
            
            Tractor tractor = tractorDao.getByVamid(vehicleBind.getTractorId());
            if (tractor == null || !tractor.isBind()) {
                throw BusinessException.newInstance("500", "牵引车不存在或未绑定挂车");
            }
            
            VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
            
            // 1. delete vehicle_source
            genericDaoSupport.update(vehicleSource.disable(operator));
            
            // 2. update tractor && trailer
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
            if (vehicleSource.hasTrailer()) {
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
            // 3. lock vehicle
            if(vehicleSource.couldLock()) {
                this.lockVehicle(vehicleSource, "解绑整车，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "解绑车辆锁定");
            }
            
            // 4. save vehicle_source event
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
        }
        
    }
    
    /**
     * @param tractor
     * @param vamTrailerId
     */
    private void validBind(Tractor tractor, Long vamTrailerId) {
        
        // 1.1 determine trailer whether required
        if (tractor.requireTrailer() && vamTrailerId == null) {
            throw BusinessException.newInstance("500", "该牵引车必须绑定挂车");
        }
        
        // 1.2 valid vehicle_source
        if (vamTrailerId != null) {
            Trailer trailer = trailerDao.getByVamid(vamTrailerId);
            VehicleSource existVehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
            if (existVehicleSource != null) {
                throw BusinessException.newInstance("500", "整车已存在，挂车id重复");
            }
        }
        VehicleSource existVehicleSource = vehicleSourceDao.getByBrand(tractor.getBrandCNCode());
        if (existVehicleSource != null) {
            throw BusinessException.newInstance("500", "整车已存在，牵引车中文车牌号重复");
        }
        existVehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
        if (existVehicleSource != null) {
            throw BusinessException.newInstance("500", "整车已存在，牵引车id重复");
        }
        
    }
    
    /**
     * @param vehicleSource
     * @return
     */
    private VehicleSource syncOldVehicleSource(VehicleSource vehicleSource) {
        
        String brand = vehicleSource.getTractorBrandCode();
        
        // 查询是否存在已disable，车牌号为新建车辆车牌号，code不为空的车辆
        VehicleSource oldVehicle = vehicleSourceDao.getOldVehicleByBrand(brand);
        if(oldVehicle != null) {
            
            // 更新新建车辆锁定信息
            boolean couldUnlock = StringUtils.isBlank(this.validUnlock(vehicleSource));
            if (BooleanUtils.isTrue(oldVehicle.getLock()) && !couldUnlock) {
                vehicleSource.syncLockInfo(oldVehicle.getLock(), oldVehicle.getLockType(), oldVehicle.getLockReason());
            }
            
        }
        
        return vehicleSource;
        
    }
    
    /**
     * @param vehicleSource
     * @return
     */
    private String validUnlock(VehicleSource vehicleSource) {
        
        if(!vehicleSource.isSynchronize()) {
            return "车辆暂未同步";
        }
        
        if(StringUtils.isEmpty(vehicleSource.getCode())) {
            return "车辆暂未同步，数据异常";
        }
        
        Vehicle veh = vehicleDao.loadByCode(vehicleSource.getCode());
        List<Driver> drivers = driverDao.listByVehicle(veh.getId(), DriverAuditStatus.QUALIFIED);
        if (org.apache.commons.collections.CollectionUtils.isEmpty(drivers)) {
            return "车辆未和司机绑定";
        }
        
        Date twoHoursBefore = DateUtils.addHours(new Date(), -2);
        if(!this.hasLastTwoHourGpsInfo(vehicleSource, twoHoursBefore)) {
            return "无最近两小时的GPS信息";
        }
        
        return null;
        
    }
    
    /**
     * @param vehicleSource
     * @param twoHoursBefore
     * @return
     */
    private boolean hasLastTwoHourGpsInfo(VehicleSource vehicleSource, Date twoHoursBefore) {
        
        // 1. get vw_vehicle info
        String vehicleCode = vehicleSource.getCode();
        Vehicle vehicle = vehicleDao.loadByCode(vehicleCode);
        
        // 2. get vw_vehicle whether has gps info in 2 hour
        List<VehicleGpsInfo> infos = remoteTransMeansDao.listLatestGps(vehicle.getId(), twoHoursBefore);
        return org.apache.commons.collections.CollectionUtils.isNotEmpty(infos);
        
    }
    
    /**
     * 对原已同步的车辆（非冻结），绑定后直接变已同步状态
     * 
     * @param vehicleSource
     * @param operator
     * @param now
     */
    private void sync(VehicleSource vehicleSource, Long operator, Date now) {
        
        Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());
        if (!tractor.getStatus().isQualified()) {
            return;
        }
        if (vehicleSource.hasTrailer()) {
            Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
            if (!trailer.getStatus().isQualified()) {
                return;
            }
        }
        
        if (vehicleSource.hasCode()) {
            
            LockType lockType = vehicleSource.getLockType();
            if (!lockType.isFreeze()) {
                
                // 1 edit vehicle source
                genericDaoSupport.update(vehicleSource.sign(operator, now));
                
                genericDaoSupport.update(tractor.sign());
                
                if (vehicleSource.hasTrailer()) {
                    Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                    genericDaoSupport.update(trailer.sign());
                }
                
                // 2 edit remote transMeans
                String vehicleCode = vehicleSource.getCode();
                RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
                if(remoteTransMeans != null) {
                    genericDaoSupport.update(remoteTransMeans.editGps(tractor.getSimCard(), now));
                }
                
                // 3 unlock vehicle_source
                if (vehicleSource.couldUnlock()) {
                    this.unlockVehicle(vehicleSource, operator, "挂车绑定解锁");
                }
                
                // 4. save vehicle_source event
                genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.SYNC, operator));
                
            }
            
        }
        
    }
    
    /**
     * @param vehicleSource
     * @param operator
     * @param description
     */
    private void unlockVehicle(VehicleSource vehicleSource, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. unlock fms vehicle
        genericDaoSupport.update(vehicleSource.unlock(operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.unlock(Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.UNLOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.queue.service.RemoteVehicleService#delete(com.demo2do.fms.queue.entity.VehicleDelete)
     */
    public void delete(VehicleDelete vehicleDelete) {
        
        Long operator = vehicleDelete.getOperator();
        if (operator == null) {
            throw BusinessException.newInstance("500", "操作人不能为空");
        }
        
        if (vehicleDelete.isTractor() || vehicleDelete.isRescue()) {
            
            Tractor tractor = tractorDao.getByVamid(vehicleDelete.getVamId());
    
            if (tractor.getStatus().isModified()) {
                throw BusinessException.newInstance("500", "变更申报车辆，无法删除");
            } else if (tractor.isSynchronize()) {
                this.out(tractor, operator);
            } else {
                this.delete(tractor, operator);
            }
            
        } else {
            
            Trailer trailer = trailerDao.getByVamid(vehicleDelete.getVamId());
            
            if (trailer.getStatus().isModified()) {
                throw BusinessException.newInstance("500", "变更申报车辆，无法删除");
            } else if (trailer.isSynchronize()) {
                this.out(trailer, operator);
            } else {
                this.delete(trailer, operator);
            }
            
        }
        
    }

    /**
     * @param tractor
     * @param operator
     */
    private void delete(Tractor tractor, Long operator) {

        // 如果同步过了走转出逻辑
        if(tractor.isSynchronize()){

            logger.info("#delete() tractor is synchronize. execute the out logic");

            this.out(tractor, operator);

            return;
        }

        Date now = new Date();
        
        // 1. delete tractor
        genericDaoSupport.update(tractor.disable(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.DELETE, operator));
        
        if (tractor.isBind()) {
            
            // 2. delete vehicle_source
            VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
            genericDaoSupport.update(vehicleSource.disable(operator));
            
            // 3. unbind trailer
            if (vehicleSource.hasTrailer()) {
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
        }
        
    }

    /**
     * @param trailer
     * @param operator
     */
    private void delete(Trailer trailer, Long operator) {
        
        Date now = new Date();
        
        // 1. delete trailer
        genericDaoSupport.update(trailer.disable(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.DELETE, operator));
        
        if (trailer.isBind()) {
            
            // 2. delete vehicle_source
            VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
            genericDaoSupport.update(vehicleSource.disable(operator));
            
            // 3. unbind tractor
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
        }
        
    }
    
    /**
     * @param tractor
     * @param operator
     */
    private void out(Tractor tractor, Long operator) {
        
        Date now = new Date();
        
        // 1. 判断是否存在已同步整车
        VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
        if (vehicleSource != null && vehicleSource.isSynchronize()) {
            
            // 1.1 锁定MDM车辆
            this.lockVehicle(vehicleSource, "转出牵引车冻结", LockType.FREEZE, operator, "转出冻结");
            
            // 1.2 删除司机和车辆绑定关系
            Vehicle vehicle = vehicleDao.loadByCode(vehicleSource.getCode());
            if(vehicle != null) {
                List<Driver> drivers = driverDao.listByVehicle(vehicle.getId(), DriverAuditStatus.QUALIFIED);
                for(Driver driver : drivers) {
                    genericDaoSupport.update(driver.unbind(operator, now));
                }
            }
            
        }
        
        // 2.1 删除整车
        if (tractor.isBind()) {
            
            genericDaoSupport.update(vehicleSource.out(operator));
            
            // 2.2 解绑挂车
            if (vehicleSource.hasTrailer()) {
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
        }
        
        // 3. 删除牵引车
        genericDaoSupport.update(tractor.out(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.OUT, operator));
        
    }
    
    /**
     * @param trailer
     * @param operator
     */
    private void out(Trailer trailer, Long operator) {
        
        Date now = new Date();
        
        // 1. 判断是否存在已同步整车
        VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
        if (vehicleSource != null && vehicleSource.isSynchronize()) {
            this.lockVehicle(vehicleSource, "挂车解绑，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "挂车解绑，调度异常锁定");
        }
        
        // 2.1 删除整车
        if (trailer.isBind()) {
            
            genericDaoSupport.update(vehicleSource.out(operator));
            
            // 2.2 解绑牵引车
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
        }
        
        // 3. 删除挂车
        genericDaoSupport.update(trailer.out(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.OUT, operator));
        
    }
    
    /**
     * @param vehicleSource
     * @param reason
     * @param type
     * @param operator
     * @param description
     */
    private void lockVehicle(VehicleSource vehicleSource, String reason, LockType type, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. lock fms vehicle
        genericDaoSupport.update(vehicleSource.doLock(reason, type, operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.lock(type, Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.LOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
}
